/** @file    SocketUdpLS.cpp
 *  @author  Alessandro Polo
 *  @version $Id: SocketUdpLS.cpp 2821 2010-08-01 11:44:26Z alex $
 *  @brief
 * File containing methods for the wosh::network::libsockets::SocketUdpLS class.
 * The header for this class can be found in SocketUdpLS.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/
 
 #include <framework/network/libsockets/SocketUdpLS.h>

 #include <core/ObjectAllocator.h>
 #include <Sockets/UdpSocket.h>
 #include <Sockets/Utility.h>
 #include <Sockets/SocketHandler.h>

using namespace std;
namespace wosh {
 namespace network {
  namespace libsockets {

class SocketUdpLSWrapper : public Sockets::UdpSocket {
	public:
		SocketUdpLSWrapper( Sockets::SocketHandler& m ) : Sockets::UdpSocket(m) {
			this->parent = NULL;
		 }
		virtual ~SocketUdpLSWrapper() { }

	public:

		void OnRawData(const char *p,size_t l, struct sockaddr *sa_from, socklen_t sa_len ) {
			if ( this->parent != NULL )
				this->parent->OnRawData(p,l,sa_from,sa_len);
		 }

		wosh::network::libsockets::SocketUdpLS* parent;

};


 WOSH_REGISTER(wosh::network::libsockets::SocketUdpLS, "wosh::network::SocketUdp", 1.0, _static_SocketUdpLS )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

SocketUdpLS::SocketUdpLS( ) : wosh::ThreadImpl(NULL, "SocketUdpLS.Thread"), wosh::network::SocketUdp()
 {
	// should go in runThread..
	this->sHandler = new Sockets::SocketHandler();
	this->socketWapper = new SocketUdpLSWrapper(*this->sHandler);
	this->socketWapper->parent = this;
 }

SocketUdpLS::~SocketUdpLS()
 {
	if ( this->isRunning() )
		unbindSocket();

	delete this->socketWapper; this->socketWapper = NULL;

	delete this->sHandler; this->sHandler = NULL;
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// BINDING

WRESULT SocketUdpLS::bindSocket( unsigned long timeout ) {
	if ( isThreadRunning() ) {
		Log(LOG_CRITICAL, ":bindSocket() Already Binded!" );
		return WRET_ERR_WRONG_STATE;
	 }
	Log(LOG_VERBOSE, ":bindSocket(%s,%d) ", this->address.c_str(), timeout );
	WRESULT ret = startThread(timeout);
	if ( WFAILED(ret) )
		Log(LOG_CRITICAL, ":bindSocket(%s,%d) Listening FAILED#%d [TimedOut]!", this->address.c_str(), timeout, ret );
	else {
		if ( timeout == 0 )
			Log(LOG_VERBOSE, ":bindSocket(%s) Asynch Listening..", this->address.c_str() );
		else
			Log(LOG_INFO, ":bindSocket(%s) Listening succedded.", this->address.c_str() );
	 }
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT SocketUdpLS::unbindSocket( unsigned long timeout ) {
	if ( !isThreadRunning() ) {
		Log(LOG_WARNING, ":unbindSocket() Not Binded!" );
		return WRET_ERR_WRONG_STATE;
	 }
	Log(LOG_VERBOSE, ":unbindSocket(%d) ", timeout );
	WRESULT ret = ThreadImpl::quitThread(timeout);
	if ( WFAILED(ret) )
		Log(LOG_CRITICAL, ":unbindSocket(%d) FAILED#%d [TimedOut]!", timeout, ret );
	else {
		if ( timeout == -1 )
			Log(LOG_VERBOSE, ":unbindSocket() Asynch UnBinding.." );
		else
			Log(LOG_INFO, ":unbindSocket() UnBinding succedded." );
	 }
	return ret;
 }

///////////////////////////////////////////////////////////////////////////////////////////////////// BINDING
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////// THREAD RUN

bool SocketUdpLS::initializingThread()
 {
	Log(LOG_VERBOSE, ":initializingThread() Binding Socket to '%s' at port %d..", this->address.c_str(), this->port );
	port_t p = (unsigned int)this->port;
	int binded = -1;
	if ( this->address == "0.0.0.0" ) {
		binded = this->socketWapper->Bind( p );
	 }
	else {
		Log(LOG_CRITICAL, ":initializingThread() WARNING NOT TESTED " __FILE__ " line %d", __LINE__ );
		binded = this->socketWapper->Bind( this->address, p );
	 }
	//int binded = Bind(INTERFACE/IP, this->port);
	if ( binded == -1 ) {
		Log(LOG_CRITICAL, ":initializingThread() FAILED : Binding Socket to '%s' at port %d.. []", this->address.c_str(), p );
		return false;
	 }
	this->sHandler->Add(this->socketWapper);
	this->sHandler->Select(1,0);
	return true;
 }

void SocketUdpLS::runThread() {
	while( this->running && this->sHandler->GetCount() ) {
		this->sHandler->Select(1,0);
	 }
 }

void SocketUdpLS::exitingThread() { 
	Log(LOG_VERBOSE, ":exitingThread() Closing Socket.." );
	int closed = this->socketWapper->Close();
	if ( closed ) {
		Log(LOG_CRITICAL, ":exitingThread() FAILED#%d Closing Socket..", closed );
	 }
 }

////////////////////////////////////////////////////////////////////////////////////////////////// THREAD RUN
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////// INCOMING DATAGRAMS

void SocketUdpLS::OnRawData(const char *p,size_t l,struct sockaddr *sa_from,int sa_len)
{
	if ( this->listener == NULL ) {
		QThread::sleep(10);
		Log(LOG_VERBOSE, ":processPendingDatagrams() IGNORING Pending Datagram!" );
		return;
	 }
	std::string ip;
	int senderPort = 0;

	if (sa_len == sizeof(struct sockaddr_in)) { // IPv4
		struct sockaddr_in sa;
		memcpy(&sa,sa_from,sa_len);
		ipaddr_t a;
		memcpy(&a,&sa.sin_addr,4);
		Sockets::Utility::l2ip(a,ip);
		senderPort =  ntohs(sa.sin_port);
		//printf("Received %d bytes from: %s:%d\n", l,ip.c_str(),ntohs(sa.sin_port));
		//printf("%s\n",static_cast<std::string>(p).substr(0,l).c_str());
	 }
#ifdef ENABLE_IPV6
#ifdef IPPROTO_IPV6
	else if (sa_len == sizeof(struct sockaddr_in6)) { // IPv6
		struct sockaddr_in6 sa;
		memcpy(&sa,sa_from,sa_len);
		std::string ip;
		Sockets::Utility::l2ip(sa.sin6_addr,ip);
		senderPort =  ntohs(sa.sin6_port);
		//printf("(IPv6) Received %d bytes from: %s:%d\n", l,ip.c_str(),ntohs(sa.sin6_port));
		//printf("%s\n",static_cast<std::string>(p).substr(0,l).c_str());
	 }
#endif // IPPROTO_IPV6
#endif // ENABLE_IPV6

	std::string senderAdd = Utilities::format("%s:%d", ip.c_str(), senderPort);
	if ( this->ignoreSelf ) {
		if ( senderPort == this->port && senderAdd == this->address ) {
			Log(LOG_VERBOSE, ":processPendingDatagrams() IGNORING Pending Datagram from '%s' : %d!", senderAdd.c_str(), senderPort );
			return;
		 }
	 }

	this->listener->udpIncomingDatagram( p, l, senderAdd, this );
}

////////////////////////////////////////////////////////////////////////////////////////// INCOMING DATAGRAMS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////// OUTGOING DATAGRAMS

int SocketUdpLS::sendDatagram( const std::string& destination, const char* data, int size )
 {
	string ip = wosh::network::NetworkUtilities::getAddressFromDestination(destination);
	int port = wosh::network::NetworkUtilities::getPortFromDestination(destination);
	if ( port <= 0 )
		return -1;

	this->socketWapper->SendToBuf(ip, port, data, size);
	return size;
  }

////////////////////////////////////////////////////////////////////////////////////////// OUTGOING DATAGRAMS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool SocketUdpLS::isBinded() const {
	return (isThreadRunning() && this->running);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

  }; // namespace libsockets
 }; // namespace network
}; // namespace wosh
